﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Sponsorship.Ui.Wpf.Messaging
{
    public class Messenger : IMessenger
    {
        private static Messenger _instance;
        private static readonly object LockObject = new object();

        private readonly Dictionary<Type, List<ActionIdentifier>> _references = new Dictionary<Type, List<ActionIdentifier>>();

        private Messenger()
        {
        }

        public static Messenger Instance
        {
            get
            {
                lock (LockObject)
                {
                    if (_instance == null)
                        _instance = new Messenger();
                    return _instance;
                }
            }
        }

        public void Register<TNotification> (object recipient, Action<TNotification> action)
        {
            Register (recipient, null, action);
        }

        public void Register<TNotification> (object recipient, string identCode, Action<TNotification> action)
        {
            Type messageType = typeof (TNotification);

            if (!_references.ContainsKey (messageType))
                _references.Add (messageType, new List<ActionIdentifier>());

            var actionIdent = new ActionIdentifier();
            actionIdent.Action = new WeakReferenceAction<TNotification> (recipient, action);
            actionIdent.IdentificationCode = identCode;

            _references[messageType].Add (actionIdent);
        }

        public void Send<TNotification> (TNotification notification)
        {
            Type type = typeof (TNotification);
            List<ActionIdentifier> typeActionIdentifiers = _references[type];
            foreach (var ai in typeActionIdentifiers)
            {
                var actionParameter = ai.Action as IActionParameter;
                if (actionParameter != null)
                    actionParameter.ExecuteWithParameter (notification);
                else
                    ai.Action.Execute();
            }
        }

        public void Send<TNotification> (TNotification notification, string identCode)
        {
            Type type = typeof (TNotification);
            List<ActionIdentifier> typeActionIdentifiers = _references[type];
            foreach (var ai in typeActionIdentifiers)
            {
                if (ai.IdentificationCode == identCode)
                {
                    var actionParameter = ai.Action as IActionParameter;
                    if (actionParameter != null)
                        actionParameter.ExecuteWithParameter (notification);
                    else
                        ai.Action.Execute();
                }
            }
        }

        public void Unregister<TNotification> (object recipient)
        {
            Unregister<TNotification> (recipient, null);
        }

        public void Unregister<TNotification> (object recipient, string identCode)
        {
            bool lockTaken = false;

            try
            {
                Monitor.Enter (_references, ref lockTaken);
                foreach (var targetType in _references.Keys)
                {
                    foreach (var wra in _references[targetType])
                    {
                        if (wra.Action != null && wra.Action.Target != null && wra.Action.Target.Target == recipient)
                            if (String.IsNullOrEmpty (identCode) || (!String.IsNullOrEmpty (identCode) && !String.IsNullOrEmpty (wra.IdentificationCode) && wra.IdentificationCode.Equals (identCode)))
                                wra.Action.Unload();
                    }
                }
            }
            finally
            {
                if (lockTaken)
                    Monitor.Exit (_references);
            }
        }
    }
}